package dbmanager

import (
	"context"

	"gitee.com/night-tc/gobige/global"
	"gitee.com/night-tc/gobige/logger"
	"gitee.com/night-tc/gobige/msgdef/protomsg"
)

// DBPlayer.go 文件定义了玩家在数据库中的相关操作管理。
// 主要包括：
// 1. DBPlayer：管理玩家所有服务模块的加载、保存、同步等操作。
// 2. 提供了从Redis和数据库加载数据的逻辑，支持同步加载和延时加载。
// 3. 支持数据保存、异常处理、模块加载完成后的回调等功能。

/*
DBPlayer 用于管理玩家在数据库中的相关操作，包括数据加载、保存、同步等。
*/

// GetData 定义了一个回调函数类型，用于处理从数据库获取的数据。
type GetData func(tmpli map[string]IDataBaseModel) bool

// IPlayer 定义了玩家接口，包含获取玩家ID、获取服务模块、设置回调函数等方法。
type IPlayer interface {
	GetDBID() uint64
	// GetServiceModel 根据模块名称获取服务模块
	GetServiceModel(name string) (IServiceModel, bool)
	// SetBackFunc 设置回调函数
	SetBackFunc(key uint64, f GetData)
	// PopBackFunc 获取并移除回调函数
	PopBackFunc(key uint64) GetData
}

/*
DBPlayer 是每个玩家身上的数据库管理对象，用于管理所有实现了 IServiceModel 接口的模块。
*/
type DBPlayer struct {
	// 基础用户对象
	player  IServicePlayer
	pid     uint64
	spaceid uint64
	sid     uint64
	// 是否加载同步模块完成
	IsLoadAllSync bool
	// 是否加载所有模块
	IsLoadAll bool
	// 是否发生了异常
	IsPanic bool

	// 模块列表
	ModelList map[string]IServiceModel
	// 同步加载模块的加载顺序列表
	SyncModelList []IServiceModel
	// 延时加载的模块
	DelayLoadLi map[global.ServerTypeEnum][]IServiceModel

	// 需要保存的模块
	DirtyModels []IServiceModel
	// 多系统或多次请求时对应的回调方法
	flist map[uint64]GetData
	// 加载完成后的逻辑
	onLoaded func()
}

// NewDBPlayer 创建一个新的 DBPlayer 实例
func NewDBPlayer(player IServicePlayer, sid uint64, f func()) *DBPlayer {
	result := &DBPlayer{
		pid:           player.GetDBID(),
		spaceid:       player.GetSpaceID(),
		sid:           sid,
		ModelList:     make(map[string]IServiceModel),
		SyncModelList: make([]IServiceModel, 0, 10),
		DelayLoadLi:   make(map[global.ServerTypeEnum][]IServiceModel),
		flist:         make(map[uint64]GetData),
		onLoaded:      f,
		IsLoadAllSync: false,
		IsLoadAll:     false,
	}
	result.player = player
	result.AddServiceModel(player)
	// 注册DBserver的消息
	result.player.RegMsgProc(NewOtherSrvMsgProc(player))

	return result
}

// SetOnLoaded 设置加载完成后的回调函数
func (this *DBPlayer) SetOnLoaded(f func()) {
	this.onLoaded = f
}

// SetBackFunc 设置回调函数
func (this *DBPlayer) SetBackFunc(key uint64, f GetData) {
	this.flist[key] = f
}

// PopBackFunc 获取并移除回调函数
func (this *DBPlayer) PopBackFunc(key uint64) GetData {
	if result, ok := this.flist[key]; ok {
		delete(this.flist, key)
		return result
	}
	return nil
}

// SetPanic 标记为异常状态
func (this *DBPlayer) SetPanic() {
	this.IsPanic = true
}

// GetIsPanic 获取是否处于异常状态
func (this *DBPlayer) GetIsPanic() bool {
	return this.IsPanic
}

// AddServiceModel 添加服务模块，并按加载顺序进行管理
func (this *DBPlayer) AddServiceModel(v IServiceModel) {
	if _, ok := this.ModelList[v.GetServiceName()]; !ok {
		this.ModelList[v.GetServiceName()] = v

		if lstype := v.GetLoadServerType(); lstype == global.ServerTypeDefault ||
			lstype == global.ServerTypeDB {
			this.SyncModelList = append(this.SyncModelList, v)
		} else {
			arr := this.DelayLoadLi[lstype]
			arr = append(arr, v)
			this.DelayLoadLi[lstype] = arr
		}
		if v.GetServiceName() != this.player.GetServiceName() {
			v.GetDataArgs().SetSaveF(func() {
				this.DirtyModels = append(this.DirtyModels, v)
			})
		}
	} else {
		logger.Warn("试图添加重复服务：", v.GetServiceName())
	}
}

// GetServiceModel 根据模块名称获取服务模块
func (this *DBPlayer) GetServiceModel(name string) (IServiceModel, bool) {
	if v, ok := this.ModelList[name]; ok {
		return v, true
	} else {
		return nil, false
	}
}

// Init 初始化加载数据，如果加载失败则走回调逻辑
func (this *DBPlayer) Init(tmpli map[string]IDataBaseModel) (ok bool) {
	if this.player.GetIsFailLoad() {
		return false
	}
	if tmpli != nil {
		this.dbLoad(tmpli)
		return true
	} else {
		global.GetSrvInst().GetThreadGo().GoTry(this.redisLoad, func(errinter interface{}) {
			logger.Debugf(logger.LogKey_DB, "player:%s redisLoad fail err:%s", this.player, errinter)
			this.player.CallDelay(func() {
				this.SetPanic()
				this.player.OnPanic(Err_Player_Load.NewErr(errinter.(error), this.player))
			})
		}, nil)
		return false
	}
}

// initEnd 初始化结束后的处理逻辑
func (this *DBPlayer) initEnd() {
	this.player.CallDelay(func() {
		if this.onLoaded != nil {
			this.onLoaded()
		}
		// 完成加载后，可以进行数据同步
		this.AllDataSync()
		this.IsLoadAllSync = true
		// 最后启动延时加载的模块的加载逻辑
		this.DelayLoads()
	})
}

// redisLoad 从Redis加载数据
func (this *DBPlayer) redisLoad(context.Context) {
	ok := true
	tabli := make([]string, 0, 10)
	for _, sv := range this.SyncModelList {
		if !ok {
			// 只要加载失败了，后面的模块就都需要重新加载，因为可能有依赖
			tabli = append(tabli, sv.GetTableNames()...)
		} else if sv.GetIsLoaded() {
			// 加载过就跳过
			continue
		} else if lis, err := sv.GetRedisTableDataAllDoMD(sv.GetTableNames()); err == nil {
			mg := sv
			this.player.CallDelay(func() {
				mg.LoadData(lis)
				mg.InitLoaded()
			})
		} else {
			// 如果当前这个加载失败了
			tabli = append(tabli, sv.GetTableNames()...)
			ok = false
		}
	}
	if ok {
		this.initEnd()
		return
	}
	msg := new(protomsg.DBGetPlayerInfoReq)
	msg.PID = this.pid
	msg.EntityID = this.player.GetEntityID()
	msg.SpaceID = this.spaceid
	msg.SrvID = this.sid
	msg.BackKey = global.GetSrvInst().GetNewEID()
	this.SetBackFunc(msg.BackKey, this.Init)
	msg.Tables = tabli
	PostDBPlayer(this.pid, this.player.GetEntityID(), msg)
}

// dbLoad 从数据库加载数据
func (this *DBPlayer) dbLoad(tmpli map[string]IDataBaseModel) {
	lis := make(map[string][]IDataBaseModel)
	for _, v := range tmpli {
		if tli, ok := lis[v.TableName()]; !ok {
			tli = make([]IDataBaseModel, 1, 10)
			tli[0] = v
			lis[v.TableName()] = tli
		} else {
			tli = append(tli, v)
			lis[v.TableName()] = tli
		}
	}
	for _, sv := range this.SyncModelList {
		if sv.GetIsLoaded() {
			// 加载过就跳过
			continue
		} else {
			sv.LoadDB(lis)
			sv.InitLoaded()
		}
	}
	this.initEnd()
}

// Save 保存脏数据到数据库
func (this *DBPlayer) Save() {
	if !this.IsLoadAllSync {
		return
	}
	if this.GetIsPanic() {
		return
	}
	this.player.SyncData()
	for _, v := range this.DirtyModels {
		v.SyncData()
	}
	clear(this.DirtyModels)
	this.DirtyModels = this.DirtyModels[:0]
}

// AllDataSync 同步所有数据给客户端
func (this *DBPlayer) AllDataSync() {
	for _, v := range this.SyncModelList {
		v.AllDataSyncClient()
	}
}

// AllDelayDataSync 同步延时加载的数据
func (this *DBPlayer) AllDelayDataSync() {
	for _, limg := range this.DelayLoadLi {
		for _, v := range limg {
			v.AllDataSyncClient()
		}
	}
}

// DelayLoads 启动延时加载逻辑
func (this *DBPlayer) DelayLoads() {
	for stype, mgli := range this.DelayLoadLi {
		for _, mg := range mgli {
			if stype == global.ServerTypeDB {
				if lis, err := mg.GetRedisTableDataAllDoMD(mg.GetTableNames()); err != nil {
					mgr := new(DelayLoadMgr)
					mgr.IServiceModel = mg
					mgr.SendDBLoadMsg(this)
				} else {
					mg.LoadData(lis)
					mg.InitLoaded()
				}
			} else {
				// 去别的服务器获取数据
				this.sendLoadByOther(stype, mg)
			}
		}
	}
	this.CheckLoaded()
}

// sendLoadByOther 向其他服务器发送请求以获取数据
func (this *DBPlayer) sendLoadByOther(stype global.ServerTypeEnum, mg IServiceModel) {
	user := this.player
	msg := new(protomsg.OtherGetPlayerInfoReq)
	msg.PID = this.pid
	msg.SpaceID = this.spaceid
	msg.SrvID = global.GetCfgInst().GetServerID()
	msg.BackKey = global.GetSrvInst().GetNewEID()
	msg.SrvNames = make([]string, 0)
	user.SetBackFunc(msg.BackKey, func(tmpli map[string]IDataBaseModel) bool {
		lis := make(map[string][]IDataBaseModel)
		for _, v := range tmpli {
			if tli, ok := lis[v.TableName()]; !ok {
				tli = make([]IDataBaseModel, 1, 10)
				tli[0] = v
				lis[v.TableName()] = tli
			} else {
				tli = append(tli, v)
				lis[v.TableName()] = tli
			}
		}
		mg.LoadDB(lis)
		mg.InitLoaded()
		return true
	})
	msg.SrvNames = append(msg.SrvNames, mg.GetServiceName())
	_ = user.Post(stype, msg)
}

// OnLoadedAll 所有系统加载完成后的调用
func (this *DBPlayer) OnLoadedAll() {
	this.IsLoadAll = true
}

// CheckLoaded 检查是否所有模块都已加载完成
func (this *DBPlayer) CheckLoaded() bool {
	if this.IsLoadAll {
		return true
	}
	for _, v := range this.ModelList {
		if !v.GetIsLoaded() {
			return false
		}
	}
	this.player.OnLoadedAll()
	return true
}
